package MathText
{
	import flash.events.ErrorEvent;
	import flash.events.EventDispatcher;
	
	/** This object constructs and stores the properties of the word problem */
	public class WordProblem extends EventDispatcher
	{
		private static const MAX_INTEGRATION_ATTEMPTS:int = 100;
		// private objects
		private var subjects:Array;
		private var quantityArray:Array; // Array with (for these problems) three quantities, last should be combined value
		//private var objects:Array, subjects:Array, quantities:Array; // the actual context words
		//private var o_holders:Array, s_holders:Array, q_holders:Array; // the placeholders from the problem template
		private var _text:String, _type:String;
		private var _solution:Number, _addend1:Number, _addend2:Number, _total:Number;
		private var _operation:String;
		private var _sentences:Array;
		private var _contextXML:XML;
		private var _problemTemplateXML:XML;
		// getters and setters
		public function get text():String{return _text;}
		public function get type():String{return _type;}
		public function get solution():Number{return _solution;}
		public function get total():Number{return _total;}
		public function get addend1():Number{return _addend1;}
		public function get addend2():Number{return _addend2;}
		public function get operation():String{return _operation;}
		public function get numSentences():int{return _sentences.length;}
		public function get numSentencesToModel():int {return 3;}
		public function get sentences():Array{return _sentences;}	
		public function get context():XML{return _contextXML;}	
		public function get subject1():Subject{if (subjects != null && subjects.length > 0){return subjects[0];} else { return null;}} 
		public function get subject2():Subject{if (subjects != null && subjects.length > 1){return subjects[1];} else { return null;}} 
		public function get subject3():Subject{if (subjects != null && subjects.length > 2){return subjects[2];} else { return null;}} 
		
		public function WordProblem(problemTemplateXML:XML, contextXML:XML, quantityArray:Array)
		{
			var success:Boolean = false;
			this._problemTemplateXML = problemTemplateXML;
			this._contextXML = contextXML;
			this.quantityArray = quantityArray;
			if (integrateProblemContext())
			{ 
				success = true; 
				_sentences = text.split("."); 
				// re-attach "." except last, which should end in ?
				for (var i:uint = 0; i < _sentences.length-1; i++)
				{
					_sentences[i] = _sentences[i] + ".";
				}					
			} else
			{
				this.dispatchEvent( new ErrorEvent(ErrorEvent.ERROR));
			}			
		}
		/** Will create the actual word problem as well as the subject objects that store the relevant information.
		 * Algorithm:
		 * 		Search through problem string for all unique subjects (e.g. subject_1, subject_2, etc.), get name from context, create new object.
		 * 		Go through each tag in the problemTemplate, besides subjects, and update associated subject object.
		 * 		Create word problem by replacing each word with field from subject object.
		 * 		Create correct answer.
		 */ 
		private function integrateProblemContext():Boolean
		{
			var subject:Subject; 
			var id:int;
			var numpattern:RegExp = /[0-9]+/;
			// what is type of problem
			_type = _problemTemplateXML.attribute("id")[0];
			// get rid of numbers
			_type = _type.substring(0, _type.search(/[0-9]/));
			
			_text = _problemTemplateXML.toString();
			// How many unique subjects in the problem text?
			var spattern:RegExp = /{(subject|pronoun|opronoun)_[0-9]+}/g;			
			var s_holders:Array = spliceUniqueID(_text.match(spattern));
			s_holders = sortByID(s_holders);
			// make sure there are enough subjects in the context
			if (_contextXML..subject.length() < s_holders.length) return false;
			
			// go through each subject and create an object, set subject (name, pronoun, plural, first_person)
			subjects = new Array();
			for (var i:uint=0; i < s_holders.length; i++)
			{
				var s:String = s_holders[i];
				id = int(s.match(numpattern)[0]);
				// pull the id tag off the end of the subject tag.  e.g., subject_1 -> 1
				subject = new Subject(id);
				subject.setSubject(_contextXML..subject[i]);
				subjects.push(subject);				
			}
			
			// Get all pverbs and update associates subjects
			var pvpattern:RegExp = /{pverb_(past|present|future)_[0-9]+}/g;
			var pv_holders:Array = spliceUnique(_text.match(pvpattern));
			// make sure a pverb exists in the context
			if (pv_holders.length > 0 && _contextXML..pverb.length() == 0 ) return false;
			for each (var pv:String in pv_holders)
			{
				id = int(pv.match(numpattern)[0]);				
				subject = this.getSubject(id);	
				subject.setPVerb(_contextXML..pverb[0]);
			}
			
			// Get all averbs and update associates subjects
			var avpattern:RegExp = /{averb_(past|present|future)_[0-9]+}/g;
			var av_holders:Array = spliceUnique(_text.match(avpattern));
			// make sure a pverb exists in the context
			if (av_holders.length > 0 && _contextXML..averb.length() == 0 ) return false;
			for each (var av:String in av_holders)
			{
				id = int(av.match(numpattern)[0]);
				subject = this.getSubject(id);	
				subject.setAVerb(_contextXML..averb[0]);
			}
			
			// Go through each quantity, get a value, and update associated subjects
			var qpattern:RegExp = /{[icf]quantity_[0-9]+}/g;
			var q_holders:Array = spliceUnique(_text.match(qpattern));
			for each (var q:String in q_holders)
			{
				id = int(q.match(numpattern)[0]);
				subject = this.getSubject(id);	
				//subject.setQuantity(this.getRandomQuantity(), q.substr(1,1));
				if (type == "combine" || type == "compare")
				{
					subject.setQuantity(quantityArray[id-1], q.substr(1,1));
				} else if (type == "change")
				{
					if (q.substr(1,1) == "i"){ subject.setQuantity(quantityArray[0], q.substr(1,1));}
					else if (q.substr(1,1) == "c"){ subject.setQuantity(quantityArray[1], q.substr(1,1));}
					else if (q.substr(1,1) == "f"){ subject.setQuantity(quantityArray[2], q.substr(1,1));}
				}
			}
			
			// Go through each quanitty unit and update associated subjects
			var upattern:RegExp = /{[icf]quantity_unit_[0-9]+}/g;
			var u_holders:Array = spliceUnique(_text.match(upattern));
			for each (var u:String in u_holders)
			{
				id = int(u.match(numpattern)[0]);				
				subject = this.getSubject(id);
				subject.setUnit(_contextXML..unit[0], u.substr(1,1));
			}
			//Subject(subjects[0]).print();
			
			// Replace subjects, quantity, unit, and verbs using appropriate replace functions
			spattern = /{(subject)_[0-9]+}/g;
			_text = _text.replace(spattern, replaceSubject);
			spattern = /{(pronoun)_[0-9]+}/g;
			_text = _text.replace(spattern, replacePronoun);
			spattern = /{(opronoun)_[0-9]+}/g;
			_text = _text.replace(spattern, replaceOPronoun);
			_text = _text.replace(qpattern, replaceQuantity); 
			_text = _text.replace(upattern, replaceUnit); 
			_text = _text.replace(pvpattern, replacePVerb); 
			_text = _text.replace(avpattern, replaceAVerb); 
			
			// Capitalize first word
			var periodpattern:RegExp = /\.\s/g;
			var sentences:Array = _text.split(periodpattern);
			var sentencesU:Array = new Array();
			for each (var sentence:String in sentences)
			{
				// capitalize first letter
				sentence = sentence.charAt(0).toUpperCase() + sentence.substr(1);
				sentencesU.push(sentence);
			}
			_text = sentencesU.join(". ");
			
			// find solution
			var stext:String = String(_problemTemplateXML.@solution);
			var terms:Array;
			var subject1:Subject, subject2:Subject;
			
			if (stext.search(RegExp(/\+/)) != -1)
			{
				terms = stext.split("+");
				if (terms.length < 2){ "Need two terms in solution"; return false;}
				id = int(terms[0].match(numpattern)[0]);
				subject1 = this.getSubject(id);	
				id = int(terms[1].match(numpattern)[0]);
				subject2 = this.getSubject(id);
				_operation = "+";
				_addend1 = subject1.getQuantity(String(terms[0]).substr(0,1));
				_addend2 = subject2.getQuantity(String(terms[1]).substr(0,1));
				_total = _addend1 + _addend2;
				_solution = _total;
			} else if (stext.search(RegExp(/-/)) != -1)
			{
				terms = stext.split("-");
				if (terms.length < 2){ "Need two terms in solution"; return false;}
				id = int(terms[0].match(numpattern)[0]);
				subject1 = this.getSubject(id);	
				id = int(terms[1].match(numpattern)[0]);
				subject2 = this.getSubject(id);
				_operation = "-";
				_total = subject1.getQuantity(String(terms[0]).substr(0,1));
				_addend1 =  subject2.getQuantity(String(terms[1]).substr(0,1));
				_addend2 = _total - _addend1;
				_solution = _addend2;
				//trace(type, String(terms[0]).substr(0,1), subject1.getQuantity(String(terms[0]).substr(0,1)), String(terms[1]).substr(0,1), subject2.getQuantity(String(terms[1]).substr(0,1)));
			} else 
			{
				trace ("Solution not given.");
				return false;
			}
			
			
			return true;
			
		}
		
			/** Return functions for the replace command */		
			private function replaceSubject():String
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				return subject.name;
			}
			private function replacePronoun():String
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				return subject.pronoun;
			}
			private function replaceOPronoun():String
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				return subject.opronoun;
			}
			
			private function replaceQuantity():Number
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				return subject.getQuantity(String(arguments[0]).substr(1,1));
			}
			
			private function replaceUnit():String
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				return subject.getUnit(String(arguments[0]).substr(1,1));
			}			
			
			private function replacePVerb():String
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				if (String(arguments[0]).search("past") != -1)
				{
					return subject.pverb_past;
				} else if (String(arguments[0]).search("present") != -1)
				{
					return subject.pverb_present;
				} else if (String(arguments[0]).search("future") != -1)
				{
					return subject.pverb_future;
				}else
				{
					trace("Verb needs to be assigned tense", arguments[0]);
					return "";
				}
			}
			
			private function replaceAVerb():String
			{
				var numpattern:RegExp = /[0-9]+/;
				var id:int = int(arguments[0].match(numpattern)[0]);
				var subject:Subject = this.getSubject(id);
				if (String(arguments[0]).search("past") != -1)
				{
					return subject.averb_past;
				} else if (String(arguments[0]).search("present") != -1)
				{
					return subject.averb_present;
				} else if (String(arguments[0]).search("future") != -1)
				{
					return subject.averb_future;
				}else
				{
					trace("Verb needs to be assigned tense", arguments[0]);
					return "";
				}
			}
			
		private function getSubject(id:int):Subject
		{
			if (subjects.length > 0)
			{
				for each (var s:Subject in subjects)
				{
					if (s.id == id) return s;
				}
			}
			return null;
		}
		
		private function xmlListToArray(xmlList:XMLList):Array
		{
			var arr:Array = new Array();
			for each (var c:XML in xmlList.child("*"))
			{
				arr.push(c);
			}
			return arr;
		}
		
		/** Update and return the array with only unique elements */
		private function spliceUnique (arr:Array):Array
		{
			for (var i:uint=0; i < arr.length; i++)
			{
				for (var j:uint=arr.length-1; j > i; j--)
				{
					if (arr[i] == arr[j]) arr.splice(j,1);
				}
			}
			return arr;
		}
		
		/** Update and return the array with only unique ids */
		private function spliceUniqueID (arr:Array):Array
		{
			var numpattern:RegExp = /[0-9]+/;
			for (var i:uint=0; i < arr.length; i++)
			{
				for (var j:uint=arr.length-1; j > i; j--)
				{
					var id1:int = int(arr[j].match(numpattern)[0]);
					var id2:int = int(arr[i].match(numpattern)[0]);
					if (id1 == id2) arr.splice(j,1);
				}
			}
			return arr;
		}
		
		/** Strings with digits at end will be sorted by the digit, e.g. pronoun_3 will be placed after subject_1.*/
		private function sortByID (arr:Array):Array
		{
			var numpattern:RegExp = /[0-9]+/;
			var narr:Array = new Array();
			for each (var s:String in arr){ narr.push(int(s.match(numpattern)[0])-1);} //subtract 1 from indices so will be like {2, 0, 3, 1}
			// replace arr values with
			var arr2:Array = new Array(arr.length);
			for (var i:int=0; i < narr.length; i++)
			{
				var index:int = narr[i];
				arr2[index] = arr[i];
			}
			return arr2;
		}
		
		/** Return a new array with shuffled elements */
		private function newArrayShuffle (arr:Array):Array
		{
			var arr2:Array = [];
			while (arr.length > 0) {
				arr2.push(arr.splice(Math.round(Math.random() * (arr.length - 1)), 1)[0]);
			}
			return arr2;
		}
		
	}
}